AWS IoT Device Defenderの監査レポートをLambdaとCDKでSlackに通知してみた
AWS IoT Device Defenderの監査レポートはChatbotに対応していないので、Slackへ通知するためにはスクリプトの実装が必要です。Simple Notification Service (SNS)とLambdaとCDKでSlack通知を実装します。
AWS IoT Device Defenderの設定は以下の記事で紹介しています。
環境情報
項目 | 内容 |
---|---|
OS | macOS Big Sur 11.6(20G165) |
Node.js | 14.18.1 |
TypeScript | 3.9.7 |
AWS CDK | 1.131.0 |
@slack/webhook | 6.0.0 |
Slack通知のLambdaを実装
Node Slack SDKを利用します。以下のコマンドでインストールします。
npm install @slack/webhook
Slack通知のLambdaをTypeScriptで実装します。非準拠の項目がある場合のみ、Slackにメッセージを送信します。
import { IncomingWebhook } from '@slack/webhook'; const SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL!; const AWS_REGION = process.env.AWS_REGION!; const AWS_DEVICE_DEFENDER_AUDIT_RESULT_URI = `https://${AWS_REGION}.console.aws.amazon.com/iot/home?region=${AWS_REGION}#/dd/audit/results`; interface AuditDetail { checkName: string; checkRunStatus: string; nonCompliantResourcesCount: number; totalResourcesCount: number; suppressedNonCompliantResourceCount: number; } interface Message { accountId: string; taskId: string; taskStatus: string; taskType: string; failedChecksCount: number; canceledChecksCount: number; nonCompliantChecksCount: number; compliantChecksCount: number; totalChecksCount: number; taskStartTime: number; auditDetails: AuditDetail[]; } interface Sns { Type: 'Notification'; MessageId: string; TopicArn: string; Subject: string | null; Message: string; Timestamp: string; SignatureVersion: string; Signature: string; SigningCertUrl: string; UnsubscribeUrl: string; MessageAttributes: object; } interface Record { EventSource: 'aws:sns'; EventVersion: string; EventSubscriptionArn: string; Sns: Sns; } interface Event { Records: Record[]; } export const handler = async (event: Event): Promise<void> => { const webhook = new IncomingWebhook(SLACK_WEBHOOK_URL); await Promise.all( event.Records.map(async (record) => { const message = JSON.parse(record.Sns.Message) as Message; const nonCompliantAuditDetailList = message.auditDetails .filter((auditDetail) => auditDetail.nonCompliantResourcesCount > 0) .map((auditDetail) => ({ type: 'section', text: { type: 'mrkdwn', text: `*${auditDetail.checkName}*\n` + `CheckRunStatus: ${auditDetail.checkRunStatus}\n` + `NonCompliantResourcesCount: ${auditDetail.nonCompliantResourcesCount}\n` + `TotalResourcesCount: ${auditDetail.totalResourcesCount}\n` + `SuppressedNonCompliantResourceCount: ${auditDetail.suppressedNonCompliantResourceCount}`, }, })); if (nonCompliantAuditDetailList.length > 0) { await webhook.send({ attachments: [ { color: '#e01e5a', blocks: [ { type: 'section', text: { type: 'mrkdwn', text: `<${AWS_DEVICE_DEFENDER_AUDIT_RESULT_URI}/${message.taskId}|:rotating_light: *AWS IoT Device Defender | Audit Report | ${message.taskId}*>`, }, }, { type: 'section', text: { type: 'plain_text', text: `Non-compliant audit item has been detected. (NonCompliantChecksCount: ${message.nonCompliantChecksCount})`, }, }, ...nonCompliantAuditDetailList, ], }, ], }); } }), ); };
SlackのWebhook URLを取得
Slackの管理画面でアプリを作成します。 https://api.slack.com/apps
Incoming Webhooksを有効化して、メッセージ送信先のSlackチャンネルを登録します。SlackチャンネルごとにWebhook URLが発行されるので取得しておきます。
Slack通知のCDKを実装
CDKでAWS IoT Device Defenderの監査結果を通知するSNSトピックと、SNS通知を処理してSlackにメッセージを送信するLambdaを実装します。
import * as iam from '@aws-cdk/aws-iam'; import * as iot from '@aws-cdk/aws-iot'; import * as lambda from '@aws-cdk/aws-lambda'; import { NodejsFunction } from '@aws-cdk/aws-lambda-nodejs'; import * as sns from '@aws-cdk/aws-sns'; import * as snsSubscriptions from '@aws-cdk/aws-sns-subscriptions'; import * as cdk from '@aws-cdk/core'; const SLACK_WEBHOOK_URL = 'ここにSlack Webhook URL'; export class IotDeviceDefenderStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const accountId = cdk.Stack.of(this).account; const topic = new sns.Topic(this, 'IotDeviceDefenderTopic', { topicName: 'iot-device-defender-topic', }); const iotDeviceDefenderAuditRole = new iam.Role( this, 'IotDeviceDefenderAuditRole', { roleName: 'iot-device-defender-audit-role', assumedBy: new iam.ServicePrincipal('iot.amazonaws.com'), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName( 'service-role/AWSIoTDeviceDefenderAudit', ), ], }, ); const iotDeviceDefenderAuditNotificationRole = new iam.Role( this, 'IotDeviceDefenderAuditNotificationRole', { roleName: 'iot-device-defender-audit-notification-role', assumedBy: new iam.ServicePrincipal('iot.amazonaws.com'), inlinePolicies: { 'iot-device-defender-audit-notification-policy': new iam.PolicyDocument( { statements: [ new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: ['sns:Publish'], resources: [topic.topicArn], }), ], }, ), }, }, ); const accountAuditConfiguration = new iot.CfnAccountAuditConfiguration( this, 'AccountAuditConfiguration', { accountId: accountId, auditCheckConfigurations: { authenticatedCognitoRoleOverlyPermissiveCheck: { enabled: true, }, caCertificateExpiringCheck: { enabled: true, }, caCertificateKeyQualityCheck: { enabled: true, }, conflictingClientIdsCheck: { enabled: true, }, deviceCertificateExpiringCheck: { enabled: true, }, deviceCertificateKeyQualityCheck: { enabled: true, }, deviceCertificateSharedCheck: { enabled: true, }, iotPolicyOverlyPermissiveCheck: { enabled: true, }, iotRoleAliasAllowsAccessToUnusedServicesCheck: { enabled: true, }, iotRoleAliasOverlyPermissiveCheck: { enabled: true, }, loggingDisabledCheck: { enabled: true, }, revokedCaCertificateStillActiveCheck: { enabled: true, }, revokedDeviceCertificateStillActiveCheck: { enabled: true, }, unauthenticatedCognitoRoleOverlyPermissiveCheck: { enabled: true, }, }, auditNotificationTargetConfigurations: { sns: { enabled: true, roleArn: iotDeviceDefenderAuditNotificationRole.roleArn, targetArn: topic.topicArn, }, }, roleArn: iotDeviceDefenderAuditRole.roleArn, }, ); const scheduledAudit = new iot.CfnScheduledAudit( this, 'DailyScheduledAudit', { scheduledAuditName: 'DailyScheduledAudit', frequency: 'DAILY', targetCheckNames: [ 'AUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK', 'CA_CERTIFICATE_EXPIRING_CHECK', 'CA_CERTIFICATE_KEY_QUALITY_CHECK', 'CONFLICTING_CLIENT_IDS_CHECK', 'DEVICE_CERTIFICATE_EXPIRING_CHECK', 'DEVICE_CERTIFICATE_KEY_QUALITY_CHECK', 'DEVICE_CERTIFICATE_SHARED_CHECK', 'IOT_POLICY_OVERLY_PERMISSIVE_CHECK', 'IOT_ROLE_ALIAS_ALLOWS_ACCESS_TO_UNUSED_SERVICES_CHECK', 'IOT_ROLE_ALIAS_OVERLY_PERMISSIVE_CHECK', 'LOGGING_DISABLED_CHECK', 'REVOKED_CA_CERTIFICATE_STILL_ACTIVE_CHECK', 'REVOKED_DEVICE_CERTIFICATE_STILL_ACTIVE_CHECK', 'UNAUTHENTICATED_COGNITO_ROLE_OVERLY_PERMISSIVE_CHECK', ], }, ); scheduledAudit.addDependsOn(accountAuditConfiguration); const notifySlackOfDeviceDefenderFunction = new NodejsFunction( this, 'NotifySlackOfDeviceDefenderFunction', { functionName: 'notify-slack-of-device-defender', entry: '../functions/notify-slack-of-device-defender.ts', runtime: lambda.Runtime.NODEJS_14_X, tracing: lambda.Tracing.ACTIVE, environment: { SLACK_WEBHOOK_URL: SLACK_WEBHOOK_URL, }, }, ); topic.addSubscription( new snsSubscriptions.LambdaSubscription( notifySlackOfDeviceDefenderFunction, ), ); } }
動作確認
即時実行の監査スケジュールを作成して監査結果を確認します。
非準拠の項目が検出された場合、Incoming Webhooksで指定したSlackチャンネルへメッセージが投稿されます。
タイトルのリンクをクリックすると監査結果の詳細が表示されます。
まとめ
監査で非準拠な項目がある場合だけSlackに通知させることで、設定のミスや不審な動作に気がつくことが出来るようになりました。GuardDutyなどの監視ツールではChatbotが対応しているので、AWS IoT Device Defenderもそのうち対応してもらえると嬉しいですね。